package engine

import (
	"context"
	"github.com/kataras/golog"
	"sdn-executor/config"
	"sdn-executor/utils/erros"
	"sync"
	"time"
)

type Engine struct {
	Device     string
	cancelFunc context.CancelFunc
	chanMap    map[uint]*Channel
	mutex      sync.Mutex
	ctx        context.Context
}

func NewEngine(ctx context.Context, cancelFunc context.CancelFunc, device string) *Engine {
	return &Engine{
		Device:     device,
		cancelFunc: cancelFunc,
		chanMap:    make(map[uint]*Channel),
		ctx:        ctx,
	}
}

func (e *Engine) Start() {
	for _, v := range e.chanMap {
		v.Start()
	}
}

func (e *Engine) Close() {
	e.cancelFunc()
}

func (e *Engine) SendRequest(chanName uint, m *Message) *erros.Error {
	c, err := e.GetOrCreateChannel(chanName, m)
	if err != nil {
		return err
	}
	return c.Push(m)
}

func (e *Engine) SendRequestWithTimeout(chanName uint, m *Message, timeout time.Duration) *erros.Error {
	c, err := e.GetOrCreateChannel(chanName, m)
	if err != nil {
		return err
	}
	return c.PushWithTimeout(m, timeout)
}

func (e *Engine) GetOrCreateChannel(chanName uint, m *Message) (*Channel, *erros.Error) {
	if !e.Validate(chanName, m) {
		golog.Errorf("only channel 1 support write. channel: %d, device: %s", chanName, e.Device)
		return nil, erros.New(erros.CodeInvalidQueryParam, "only channel 1 support write")
	}

	maxChannelCount := config.C().Engine.MaxChannelCount
	if chanName == 0 || chanName >= maxChannelCount {
		golog.Errorf("invalid channel, channel: %d, MaxChannelCount: %d, device: %s", chanName, maxChannelCount, e.Device)
		return nil, erros.New(erros.CodeInvalidQueryParam,
			"invalid channel, MaxChannelCount: %d", maxChannelCount)
	}
	c, ok := e.chanMap[chanName]
	if !ok {
		e.mutex.Lock()
		defer e.mutex.Unlock()
		c, ok = e.chanMap[chanName]
		if !ok {
			c = NewChannel(e.ctx, config.C().Engine.MaxChannelQueSize)
			e.chanMap[chanName] = c
			golog.Info("to start engine channel. channel: %d, device: %s", chanName, e.Device)
			c.Start()
		}
	}
	return c, nil
}

func (e *Engine) Validate(chanName uint, m *Message) bool {
	return chanName == 1 || m.ReadOnly
}
